from typing import List, Optional, Tuple
from collections import namedtuple

from django.core.mail import get_connection, EmailMultiAlternatives
from django.core.mail.backends.base import BaseEmailBackend
from django.core.mail.backends.smtp import EmailBackend as SMTPEmailBackend
from django.conf import settings
from django.utils.translation import gettext as _

from apps.app_users.models import Email
from apps.app_global.configs_manager import global_configs


_EmailConfig = namedtuple(
    'EmailConfig',
    ['host', 'port', 'host_user', 'host_password', 'use_tls', 'use_ssl']
)
class EmailConfig(_EmailConfig):
    """
    host: str
    port: int
    host_user: str
    host_password: str
    use_tls: bool
    use_ssl: bool
    """
    pass


class EmailSender:
    EmailTag = Email.Tag

    @staticmethod
    def get_connection(
        host=None,
        port=None,
        username=None,
        password=None,
        use_tls=None,
        fail_silently=False,
        use_ssl=None,
        timeout=None,
        ssl_keyfile=None,
        ssl_certfile=None,
        **kwargs,
    ) -> BaseEmailBackend:
        """
        邮件服务连接
        """
        return get_connection(
            host=host,
            port=port,
            username=username,
            password=password,
            fail_silently=fail_silently,
            use_ssl=use_ssl,
            use_tls=use_tls,
            timeout=timeout,
            ssl_keyfile=ssl_keyfile,
            ssl_certfile=ssl_certfile,
            **kwargs
        )

    @staticmethod
    def build_save_email(
            sender: str, host: str,
            subject: str, receivers: list, message: str, tag: str, html_message: str = None,
            save_db: bool = True, remote_ip: str = '', is_feint: bool = False
    ):
        """
        构建邮件记录对象实例
        """
        receiver_str = ';'.join(receivers)
        if len(receiver_str) >= 254:
            receiver_str = receiver_str[:254]
            receiver_str = receiver_str.rsplit(';', maxsplit=1)[0]

        email = Email(
            subject=subject, receiver=receiver_str, message=message,
            sender=sender, email_host=host,
            tag=tag, is_html=False, status=Email.Status.WAIT.value, status_desc='', success_time=None,
            remote_ip=remote_ip, is_feint=is_feint
        )
        if html_message:
            email.message = html_message
            email.is_html = True

        if save_db:
            email.save(force_insert=True)  # 邮件记录

        return email

    @staticmethod
    def do_send_email(
            connection: BaseEmailBackend, email: Email, save_db: bool, receivers: List[str] = None
    ) -> Optional[Exception]:
        """
        :connection: django邮件后端实例
        :save_db: True(email发送结果更新到数据库)；False(email发送结果不更新到数据库)
        :receivers: 替换接收邮件地址
        :retrun:
            None         # success
            Exception     # failed
        """
        if email.is_feint:  # 假动作，不真实发送
            return None

        if email.status == email.Status.SUCCESS.value:
            return None

        if not receivers:
            receivers = email.receiver.split(';')

        if email.is_html:
            html_message = email.message
            message = ''
        else:
            html_message = ''
            message = email.message

        from_email = email.sender
        if isinstance(connection, SMTPEmailBackend):
            if from_email != connection.username:
                from_email = connection.username

        try:
            ok = EmailSender.send_mail(
                connection=connection,
                subject=email.subject,
                message=message,
                from_email=from_email,
                receivers=receivers,
                html_message=html_message
            )
            if ok == 0:
                raise Exception('failed')
        except Exception as exc:
            email.set_send_failed(desc=str(exc), save_db=save_db)
            return exc

        email.set_send_success(desc='', save_db=save_db)
        return None

    @staticmethod
    def send_mail(
            connection: BaseEmailBackend,
            from_email: str,
            receivers: List[str],
            subject: str,
            message: str,
            html_message: Optional[str] = None,
    ) -> int:
        """
            发送邮件

            * 参数message和html_message 二选一

        :param connection: django邮件后端实例
        :param from_email: 发送邮件地址
        :param receivers: 接收邮件地址
        :param subject: 标题
        :param message: 内容
        :param html_message: html格式内容
        :return: 发送邮件数量
            0   # failed
            1   # success

        :raises: Exception  # 可能 smtplib.SMTPException
        """
        mail = EmailMultiAlternatives(
            subject=subject, body=message, from_email=from_email, to=receivers, connection=connection
        )
        if html_message:
            mail.attach_alternative(html_message, "text/html")

        return mail.send()

    @staticmethod
    def get_default_email_configs() -> EmailConfig:
        """
        全局默认邮件配置

        :raises: Exception
        """
        configs = global_configs.get_configs()
        use_ssl = configs[global_configs.ConfigName.EMAIL_USE_SSL.value]
        use_tls = configs[global_configs.ConfigName.EMAIL_USE_TLS.value]
        use_ssl = True if use_ssl == 'true' else False
        use_tls = True if use_tls == 'true' else False
        if use_tls and use_ssl:
            raise Exception(_('邮件配置信息有误，TLS和SSL加密只能有一个为"true"'))

        return EmailConfig(
            host=configs[global_configs.ConfigName.EMAIL_HOST.value],
            port=int(configs[global_configs.ConfigName.EMAIL_PORT.value]),
            host_user=configs[global_configs.ConfigName.EMAIL_HOST_USER.value],
            host_password=configs[global_configs.ConfigName.EMAIL_HOST_PASSWORD.value],
            use_tls=use_tls,
            use_ssl=use_ssl
        )

    @staticmethod
    def get_default_connection(fail_silently: bool = False, email_config: Optional[EmailConfig] = None) -> BaseEmailBackend:
        """
        全局默认邮件配置的连接
        """
        if email_config is None:
            email_config = EmailSender.get_default_email_configs()

        return EmailSender.get_connection(
            host=email_config.host,
            port=email_config.port,
            username=email_config.host_user,
            password=email_config.host_password,
            use_tls=email_config.use_tls,
            use_ssl=email_config.use_ssl,
            timeout=settings.EMAIL_TIMEOUT,
            ssl_keyfile=settings.EMAIL_SSL_KEYFILE,
            ssl_certfile=settings.EMAIL_SSL_CERTFILE,
            fail_silently=fail_silently
        )

    def send_email_default(
            self,
            subject: str, receivers: list, message: str, tag: str, html_message: str = None,
            fail_silently=True, save_db: bool = True, remote_ip: str = '', is_feint: bool = False
    ) -> Tuple[Email, Optional[Exception]]:
        """
        使用全局默认邮件配置，发送邮件

        :param subject: 标题
        :param receivers: 接收者邮箱
        :param tag: 标签
        :param message: 邮件内容
        :param html_message: html格式的邮件内容
        :param fail_silently: True(发送失败不抛出异常)
        :param save_db: True(保存邮件记录到数据库)；False(不保存)
        :param remote_ip: 客户端ip地址
        :param is_feint: True(假动作，只入库不真实发送)；False(真实发送邮件)
        :return:
            Email(), None       # 发送成功
            Email(), Exception  # 发送失败

        :raises: Exception
        """
        email_config = EmailSender.get_default_email_configs()
        connection = self.get_default_connection(fail_silently=fail_silently)
        email = self.build_save_email(
            host=email_config.host, sender=email_config.host_user,
            subject=subject, receivers=receivers, message=message, tag=tag, html_message=html_message,
            save_db=save_db, remote_ip=remote_ip, is_feint=is_feint
        )
        exc = self.do_send_email(
            connection=connection, email=email, save_db=save_db, receivers=receivers)
        if exc is not None and not fail_silently:
            raise exc

        return email, exc
